/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.appspot.ittrick.drive.trick.api;

import com.appspot.ittrick.drive.trick.api.exception.CodeExchangeException;
import com.appspot.ittrick.drive.trick.api.exception.NoUserIdException;
import com.appspot.ittrick.drive.trick.db.DBManager;
import com.appspot.ittrick.drive.trick.db.AccountMstEntity;
import static com.appspot.ittrick.drive.trick.db.DBManager.getConn;
import com.appspot.ittrick.drive.trick.model.Oauth2Model;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;

public class GoogleOauthAPIService {
    
    /**
     * シークレット情報ファイル名
     */
    private static final String CLIENTSECRETS_LOCATION = "client_secrets.json";
    private static final String CLIENT_ID = "237660039661.apps.googleusercontent.com";
    private static final String CLIENT_SECRET = "3ZEv4oXivDwFIjYeCtj8SoDd";
    /**
     * リダイレクト先URL
     */
    public static final String REDIRECT_URI = "http://localhost/";
    /**
     * アプリケーションスコープ
     */
    private static final List<String> SCOPES = Arrays.asList(
            "https://www.googleapis.com/auth/drive",
            "https://www.googleapis.com/auth/userinfo.email",
            "https://www.googleapis.com/auth/userinfo.profile");
    /**
     * Google OAuth URL
     */
    private static final String OAUTH_TOKEN_URI = "https://accounts.google.com/o/oauth2/token";
    /**
     * Google OAuth Flow
     */
    private static GoogleAuthorizationCodeFlow flow = null;
    
    /**
     * ClientSecretsの取得
     * @param jsonFactory
     * @return GoogleClientSecrets
     * @throws IOException 
     */
    private static GoogleClientSecrets getGoogleClientSecrets(JacksonFactory jsonFactory) throws IOException {
        Reader reader = new InputStreamReader(GoogleOauthAPIService.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
        return GoogleClientSecrets.load(jsonFactory, reader);
    }

    /**
     * DBから承認情報を取得する
     *
     * @param emailAddress
     * @return Credential
     * @throws NoUserIdException
     */
    public static Credential getCredentials(String emailAddress) throws Exception {

        Connection conn = getConn();
        
        // DBから承認情報を取得
        AccountMstEntity entity = DBManager.getAccountInfo(conn, emailAddress);
        // DBに存在しない場合はNULLを返す
        if(entity == null) return null;
        
        // アクセストークンを更新
        String newAccess = getNewAccessTokenByRefreshToken(entity);
        if (newAccess != null) { // アクセストールンがまだ有効の場合はNULLが返される
            entity.accessToken = newAccess;
            DBManager.updateAccountInfo(conn, entity);
        }
        
        // clientSecretsの取得
        HttpTransport httpTransport = new NetHttpTransport();
        JacksonFactory jsonFactory = new JacksonFactory();
        GoogleClientSecrets clientSecrets = getGoogleClientSecrets(jsonFactory);
        
        // 新しい承認情報の作成
        GoogleCredential credential = new GoogleCredential.Builder()
                .setJsonFactory(jsonFactory)
                .setTransport(httpTransport)
                .setClientSecrets(clientSecrets)
                .build();
        
        // DBから取得したアクセストークンとリフレッシュトークンをセット
        credential.setAccessToken(entity.accessToken).setRefreshToken(entity.refreshToken);

        return credential;
    }
    
    /**
     * Build an authorization flow and store it as a static class attribute.
     *
     * @return GoogleAuthorizationCodeFlow instance.
     * @throws IOException Unable to load client_secrets.json.
     */
    private static GoogleAuthorizationCodeFlow getFlow() throws IOException {
        if (flow == null) {
            HttpTransport httpTransport = new NetHttpTransport();
            JacksonFactory jsonFactory = new JacksonFactory();

            GoogleClientSecrets clientSecrets = getGoogleClientSecrets(jsonFactory);
            flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
                    .setAccessType("offline").setApprovalPrompt("force").build();
        }
        return flow;
    }

    /**
     * OAuth2承認用URLの取得
     *
     * @param emailAddress User's e-mail address.
     * @return Authorization URL to redirect the user to.
     * @throws IOException Unable to load client_secrets.json.
     */
    public static String getAuthorizationUrl(String emailAddress) throws IOException {
        GoogleAuthorizationCodeRequestUrl urlBuilder =
                getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI);
        urlBuilder.set("user_id", emailAddress);
        return urlBuilder.build();
    }

    /**
     * OAuth2承認用URLの取得
     * @return
     * @throws IOException 
     */
    public static String getAuthorizationUrl() throws IOException {
        GoogleAuthorizationCodeRequestUrl urlBuilder =
                getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI);
        return urlBuilder.build();
    }

    /**
     * Exchange an authorization code for OAuth 2.0 credentials.
     *
     * @param authorizationCode Authorization code to exchange for OAuth 2.0
     * credentials.
     * @return OAuth 2.0 credentials.
     * @throws CodeExchangeException An error occurred.
     */
    public static Credential exchangeCode(String authorizationCode)
            throws CodeExchangeException {
        try {
            GoogleAuthorizationCodeFlow flowTemp = getFlow();
            GoogleTokenResponse response =
                    flowTemp.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
            
            return flowTemp.createAndStoreCredential(response, null);
        } catch (IOException e) {
            System.err.println("An error occurred: " + e);
            throw new CodeExchangeException(null);
        }
    }
    
    /**
     * リフレッシュトークンから新しいアクセストークンを取得
     * @param entity
     * @return
     * @throws Exception 
     */
    private static String getNewAccessTokenByRefreshToken(AccountMstEntity entity) throws Exception {
        
        StringBuilder psb = new StringBuilder();
        psb.append("client_id=");
        psb.append(CLIENT_ID);
        psb.append("&");
        psb.append("client_secret=");
        psb.append(CLIENT_SECRET);
        psb.append("&");
        psb.append("refresh_token=");
        psb.append(entity.refreshToken);
        psb.append("&");
        psb.append("grant_type=");
        psb.append("refresh_token");
        
        Type type = new TypeToken<Oauth2Model>() {}.getType();
        Oauth2Model oauth2Model = (Oauth2Model)postGetResponseJson(
                new URL(OAUTH_TOKEN_URI),
                psb.toString(),
                type);
        
        System.out.println("New Access Token:" + oauth2Model.getAccessToken() + "/" + oauth2Model.getTokenType());
        
        return null;
    }
    
    /**
     * Json POST
     * @param postURL
     * @param postString
     * @param responseModelType
     * @return
     * @throws Exception 
     */
    private static Object postGetResponseJson(URL postURL, String postString, Type responseModelType) throws Exception {
        HttpURLConnection http = (HttpURLConnection)postURL.openConnection();
        http.setRequestMethod("POST");

        // 送信する指定
        http.setDoOutput(true);
        try (OutputStreamWriter ow = new OutputStreamWriter(http.getOutputStream(), "utf-8"); BufferedWriter bw = new BufferedWriter(ow)) {

            //POSTの内容を書き出す
            bw.write(postString);
            bw.flush();
        }
        String json;
        Gson gson;
        try (InputStreamReader ir = new InputStreamReader(http.getInputStream(), "utf-8"); BufferedReader br1 = new BufferedReader(ir)) {
            StringBuilder rsb = new StringBuilder();
            int read;
            char[] chars = new char[1024];
            while ((read = br1.read(chars)) != -1) {
                rsb.append(chars, 0, read);
            }
            json = rsb.toString();
            gson = new Gson();
        }

        return gson.fromJson(json, responseModelType);
    }
}
